package com.modbus;

import com.modbus.config.ModbusMasterConfig;
import com.modbus.entity.exception.ConnectionException;
import com.modbus.eum.ConnectionState;
import com.modbus.handler.ModbusChannelInitializer;
import com.modbus.handler.ModbusRequestHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.util.concurrent.DefaultEventExecutorGroup;
import io.netty.util.concurrent.EventExecutorGroup;
import io.netty.util.concurrent.GenericFutureListener;
import io.netty.util.concurrent.GlobalEventExecutor;
import lombok.extern.slf4j.Slf4j;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;

/**
 * @author heyayun
 * @description modbus服务
 * @date 2021/3/22 17:30
 */
@Slf4j
public class ModbusServer {
    private final ModbusMasterConfig config;
    private ServerBootstrap bootstrap;
    private Channel parentChannel;
    private ConnectionState connectionState = ConnectionState.DOWN;
    private transient final PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
    public static final String PROP_CONNECTION_STATE = "connectionState";
    private final ChannelGroup clientChannels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    public ModbusServer(ModbusMasterConfig config) {
        this.config = config;
    }

    public void setup(ModbusRequestHandler handler) throws ConnectionException {
        handler.setServer(this);
        try{
            // 1、创建线两个事件循环组
            // 处理服务器端接收客户端连接的
            final EventLoopGroup bossGroup = new NioEventLoopGroup();
            // 进行网络通信的（网络读写的）
            final EventLoopGroup workerGroup = new NioEventLoopGroup();
            // 并行执行线程数
            final EventExecutorGroup businessGroup = new DefaultEventExecutorGroup(10);
            // 2、创建辅助工具类ServerBootstrap，用于服务器通道的一系列配置
            bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                    // bossGroup通道
                    .channel(NioServerSocketChannel.class)
                    // workerGroup通道  3、在这里配置 通信数据的处理逻辑, 可以addLast多个
                    .childHandler(new ModbusChannelInitializer(handler))
                    // 客户端连接请求放在队列中等待处理队列大小，注意大于此队列，将拒绝客户端请求
                    .option(ChannelOption.SO_BACKLOG, 128)
                    // 心跳保持，默认是false
                    .childOption(ChannelOption.SO_KEEPALIVE, true);
            // 4、绑定端口, bind返回future(异步), 加上sync阻塞在获取连接处
            parentChannel = bootstrap.bind(config.getPort()).sync().channel();
            // 设置监听
            setConnectionState(ConnectionState.LISTENING);
            // 监听关闭事件
            parentChannel.closeFuture().addListener(new GenericFutureListener<ChannelFuture>(){

                @Override
                public void operationComplete(ChannelFuture channelFuture) throws Exception {
                    workerGroup.shutdownGracefully();
                    bossGroup.shutdownGracefully();
                    // 设置关闭
                    setConnectionState(ConnectionState.DOWN);
                }
            });
        } catch (Exception e) {
            setConnectionState(ConnectionState.DOWN);
            log.error(e.getMessage(),e);
            throw new ConnectionException(e.getLocalizedMessage());
        }
    }
    public void setConnectionState(ConnectionState connectionState) {
        ConnectionState oldConnectionState = this.connectionState;
        this.connectionState = connectionState;
        propertyChangeSupport.firePropertyChange(PROP_CONNECTION_STATE, oldConnectionState, connectionState);
    }
    public ConnectionState getConnectionState() {
        return connectionState;
    }

    public void addPropertyChangeListener(PropertyChangeListener listener) {
        propertyChangeSupport.addPropertyChangeListener(listener);
    }

    public void removePropertyChangeListener(PropertyChangeListener listener) {
        propertyChangeSupport.removePropertyChangeListener(listener);
    }

    public void close() {
        if (parentChannel != null) {
            parentChannel.close().awaitUninterruptibly();
        }
        clientChannels.close().awaitUninterruptibly();
    }

    public void addClient(Channel channel) {
        clientChannels.add(channel);
        setConnectionState(ConnectionState.CLIENTS_CONNECTED);
    }

    public void removeClient(Channel channel) {
        clientChannels.remove(channel);
        setConnectionState(ConnectionState.CLIENTS_CONNECTED);
    }

    public ChannelGroup getClientChannels(){
        return clientChannels;
    }
}
